﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Thor.Common
{
    /// <summary>
    /// Provides encryption/decryption functionality.
    /// </summary>
    public class Encryption
    {
        #region Constructors

        public Encryption() : this("P@ssw0rd", "P@ssw0rdS@1t") { }

        public Encryption(string encryptionPassword, string encryptionSalt)
        {
            EncryptionPassword = encryptionPassword;
            EncryptionSalt = encryptionSalt;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Default password to use for encryption.
        /// </summary>
        public string EncryptionPassword { get; set; }

        /// <summary>
        /// Default salt to use for encryption.
        /// </summary>
        public string EncryptionSalt { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Encrypts the specified plain text using the default password and salt.
        /// </summary>
        /// <param name="plainText">Text to encrypt.</param>
        /// <returns></returns>
        public string EncryptString(string plainText)
        {
            return EncryptString(plainText, EncryptionPassword, EncryptionSalt);
        }

        /// <summary>
        /// Encryptes the specified plain text using the specified password and salt.
        /// </summary>
        /// <param name="plainText">Text to encrypt.</param>
        /// <param name="encryptionPassword">Password to use for encryption.</param>
        /// <param name="encryptionSalt">Salt to use for encryption.</param>
        /// <returns></returns>
        public string EncryptString(string plainText, string encryptionPassword, string encryptionSalt)
        {
            if (!string.IsNullOrEmpty(plainText))
            {
                using (Aes aes = new AesManaged())
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(encryptionPassword, Encoding.UTF8.GetBytes(encryptionSalt));
                        aes.Key = deriveBytes.GetBytes(128 / 8);
                        memoryStream.Write(BitConverter.GetBytes(aes.IV.Length), 0, sizeof(int));
                        memoryStream.Write(aes.IV, 0, aes.IV.Length);

                        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
                        {
                            byte[] rawPlainText = Encoding.Unicode.GetBytes(plainText);
                            cryptoStream.Write(rawPlainText, 0, rawPlainText.Length);
                            cryptoStream.FlushFinalBlock();
                        }

                        byte[] cipherTextBytes = memoryStream.ToArray();
                        return Convert.ToBase64String(cipherTextBytes);
                    }
                }
            }
            return plainText;
        }

        /// <summary>
        /// Decrypts the specified cipher text using the default password and salt.
        /// </summary>
        /// <param name="cipherText">Text to decrypt.</param>
        /// <returns></returns>
        public string DecryptString(string cipherText)
        {
            return DecryptString(cipherText, EncryptionPassword, EncryptionSalt);
        }

        /// <summary>
        /// Decrypts the specified cipher text.
        /// </summary>
        /// <param name="cipherText">Text to decrypt.</param>
        /// <param name="encryptionPassword">Password to use for decryption.</param>
        /// <param name="encryptionSalt">Salt to use for decryption.</param>
        /// <returns></returns>
        public string DecryptString(string cipherText, string encryptionPassword, string encryptionSalt)
        {
            if (!string.IsNullOrEmpty(cipherText))
            {
                using (Aes aes = new AesManaged())
                {
                    byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

                    using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
                    {
                        Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(encryptionPassword, Encoding.UTF8.GetBytes(encryptionSalt));
                        aes.Key = deriveBytes.GetBytes(128 / 8);
                        aes.IV = ReadByteArray(memoryStream);

                        CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
                        using (StreamReader streamReader = new StreamReader(cryptoStream, Encoding.Unicode))
                        {
                            return streamReader.ReadToEnd();
                        }
                    }
                }
            }
            return cipherText;
        }

        /// <summary>
        /// Gets a byte array from a stream.
        /// </summary>
        /// <param name="s">Stream to get byte array from.</param>
        /// <returns></returns>
        private byte[] ReadByteArray(Stream s)
        {
            byte[] rawLength = new byte[sizeof(int)];
            if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
            {
                throw new SystemException("Stream did not contain properly formatted byte array");
            }

            byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
            if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new SystemException("Did not read byte array properly");
            }

            return buffer;
        }

        #endregion
    }
}
